/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Sky model (Singleton).
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define SKY_SKY_CC 1
#include <utility>
#include "base/module.hh"
using namespace base;
#include "gfx/module.hh"
#include "gfx/osg.hh"
using namespace gfx;
#include "glue/module.hh"
#include "glue/sim_time.hh"
using namespace glue;
#include "graph/module.hh"
#include "graph/scene_graph.hh"
using namespace graph;
#include "shader/module.hh"
#include "shader/conf.hh"
#include "shader/light.hh"
#include "shader/shader_funcs.hh"
using namespace shader;
#include "view/module.hh"
#include "view/window.hh"
using namespace view;
#include "world/module.hh"
#include "world/debug.hh"  // ASSERT_LAT_LON_DEG()
using namespace world;
#include "sky/module.hh"
#include "sky/sky.hh"
#include "sky/conf.hh"
#include "sky/defs.hh"
#include "sky/sky_dome.hh"
#include "sky/sky_dome_painter.hh"
#include "sky/clouds.hh"
#include "sky/sun.hh"
#include "sky/moon.hh"
#include "sky/stars.hh"
#include "sky/weather.hh"

namespace sky {

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////  sky functions  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Apply fog to a sky node.
 *****************************************************************************/
void
SetSkyFog( RefPtr<Node> node )
{
    // Sky needs a lower fog density than the general fog density for sky colors to be visible.
    SetShaderFog( *node, SKY_CONF.mSkyFogDensity, SHADER_CONF.mFogColor );
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////  Sky  ///////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

Sky* Sky::msInstance;

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Sky::Sky( void )
:   mSkyDome(new SkyDome( GET_WORLD().GetPosition(),
                          GET_WORLD().GetRadius(),
                          SKY_CONF.mSkyColor,
                          SKY_CONF.mSkyDomeSliceCount ) ),  // -- ORDER-DEPENDENT --
    mCloudParticleSystem0(new CloudParticleSystem(*mSkyDome,GetVolume(),4,"textures/cloud0.png")),
    mCloudParticleSystem1(new CloudParticleSystem(*mSkyDome,GetVolume(),4,"textures/cloud1.png")),
    mCloudParticleSystem2(new CloudParticleSystem(*mSkyDome,GetVolume(),4,"textures/cloud2.png")),
    mCloudParticleSystem3(new CloudParticleSystem(*mSkyDome,GetVolume(),4,"textures/cloud3.png")),
    mSun(new Sun( sky::defs::DEFAULT_SUN_LAT, sky::defs::DEFAULT_SUN_LON ) ),
    mMoon(new Moon( *mSkyDome,
                    sky::defs::DEFAULT_MOON_LAT,
                    sky::defs::DEFAULT_MOON_LON ) ),
    mStars(new Stars( GET_WORLD().GetPosition(),
                      SKY_CONF.mStarCount,
                      *mSkyDome ) ),
    mHour(glue::defs::DEFAULT_HOUR),
    mNeedToSetHour(true)
{
CHECK_TYPESIG(mSkyDome,TYPESIG_SKY_DOME);
ASSERT( GET_WORLD().GetRadius() > 0.0f );
ASSERT( SKY_CONF.mSkyDomeSliceCount > 0 );

    // NOP.  The ctor creates the Objects.
    // Later, Sky::Start() will attach them to the scene graph.

#if DEBUG
{
    // Self-check.
    std::pair<Degree,Degree> latLon;
    latLon = ComputeLatLonFromHour(  0.0f ); ASSERT( FP_EQ<fp>( latLon.first, 180.0f ) ); ASSERT( FP_EQ<fp>( latLon.second, 0.0f ) );
    latLon = ComputeLatLonFromHour(  6.0f ); ASSERT( FP_EQ<fp>( latLon.first,  90.0f ) ); ASSERT( FP_EQ<fp>( latLon.second, 0.0f ) );
    latLon = ComputeLatLonFromHour( 12.0f ); ASSERT( FP_EQ<fp>( latLon.first,   0.0f ) ); // lon doesn't matter at poles
    latLon = ComputeLatLonFromHour( 18.0f ); ASSERT( FP_EQ<fp>( latLon.first,  90.0f ) ); ASSERT( FP_EQ<fp>( latLon.second, 180.0f ) );
}
#endif
}

Sky::~Sky()
{
    // NOP
}

/*****************************************************************************
 * Make the sky.
 * This can be called again for scene reloading.
 * Clouds (being particle-systems) are too difficult to reload into scene.
 *****************************************************************************/
void
Sky::Make( void )
{
    Make( GET_SIM_TIME().GetHour() );
}

void
Sky::Make( const fp hour )
{
CHECK_TYPESIG(mSkyDome,TYPESIG_SKY_DOME);
ASSERT_HOUR(hour);

    // Loading sky into scene-graph is slow, so sky can be disabled.
    if ( SKY_CONF.mSkyEnabled )
    {
        SetHour( hour );
        ArrangeNodes();
    }
}

/*****************************************************************************
 * Attach the sky nodes to the scene graph.
 *****************************************************************************/
void
Sky::ArrangeNodes( void )
{
    // Attach the Object components of the sky to the scene graph
    // in appropriate state-sorted group nodes.
    if ( GET_WORLD().IfFlat() )
    {
        GET_SCENE_GRAPH().AttachObject( mSkyDome.PTR() );
        if ( SKY_CONF.mEnableMoon )
            GET_SCENE_GRAPH().AttachObject( mMoon.PTR() );
    }
    GET_SCENE_GRAPH().AttachObject( mStars.PTR() );
    GET_SCENE_GRAPH().AttachNode( GET_WEATHER().GetNode() );  // isn't a 3D object
}

/*****************************************************************************
 * Update sky for time-of-day.
 *****************************************************************************/
void
Sky::SetHour( const fp hour )
{
ASSERT_HOUR(hour);

    // This is a slow operation because of repainting SkyDome.
    if ( (hour == mHour) and (not mNeedToSetHour) )
        return;
    mNeedToSetHour = false;
    mHour = hour;

    // Change intensity/color of light.
    RGBA backgroundColor = SKY_CONF.mSkyColor;
    RGBA baseAmbient( 0.0f, 0.0f, 0.0f, 0.0f );
    fp brightness = 1.0f;
    const Weather::eWeather weather = GET_WEATHER().GetWeather();
    if ( weather == Weather::eWeather_RAIN )
    {
        backgroundColor = RGBA( 0.37f, 0.58f, 0.82f, 1.0f );
        baseAmbient.Set( 0.05f, 0.05f, 0.15f, 0.0f );
        brightness = 0.7f;
    }
    else if ( weather == Weather::eWeather_SNOW )
    {
        backgroundColor = RGBA( 0.57f, 0.78f, 1.0f, 1.0f );
        baseAmbient.Set( 0.05f, 0.05f, 0.15f, 0.0f );
        brightness = 0.7f;
    }
    else if ( SimTime::IfNightTime( hour ) )
    {
        backgroundColor = sky::defs::SKY_COLOR_NIGHT;
        baseAmbient.Set( 0.0f, 0.0f, 0.3f, 0.0f );
        brightness = 0.3f;
    }
    else if ( SimTime::IfMorningTime( hour ) or SimTime::IfEveningTime( hour ) )
    {
        backgroundColor = sky::defs::SKY_COLOR_TWILIGHT;
        baseAmbient.Set( 0.05f, 0.05f, 0.15f, 0.0f );
        brightness = 0.7f;
    }
#if COMPILE_OSSIM
    if ( not GET_WORLD().IfFlat() )
    {
        backgroundColor = RGBA( RGB_COSMOS_BLUE, 1.0f );
    }
#endif

    GET_SCENE_GRAPH().SetBackgroundColor( backgroundColor );  // in case sky disabled
    GET_LIGHT().SetLightBaseAmbient( baseAmbient );
    GET_LIGHT().SetLightBrightness( brightness );

    // Change position of sun.
#if 0
    mSun->SetLatLon( ComputeLatLonFromHour( hour ) );
#else
    // Make sun visible thru particular random arrangement of clouds.
    mSun->SetLatLon( std::make_pair<>( Degree(82.0f), Degree(40.0f) ) );
#endif

    // Re-paint the sky dome.
    SkyDomePainter::Paint( *mSkyDome, hour );
}

/*****************************************************************************
 * Compute (lat,lon) of sun from time-of-day.
 *****************************************************************************/
std::pair<Degree,Degree>
Sky::ComputeLatLonFromHour( const fp hour )
{
ASSERT_HOUR(hour);

    // hour  0: (lat=180,lon=0)
    // hour  6: (lat= 90,lon=0)
    // hour 12: (lat=  0,lon=0 or 180)
    // hour 18: (lat= 90,lon=180)
    // hour 24: (lat=180,lon=0 or 180)
    const Degree lat = ABS(hour - 12.0f) * (180.0f / 12.0f);
    const Degree lon = (hour < 12.0f) ? 0.0f : 180.0f;
    ASSERT_LAT_LON_DEG( lat, lon );
    return std::make_pair( lat, lon );
}

/*****************************************************************************
 * @return Volume of sky in 3D space.
 *****************************************************************************/
BoxVolume
Sky::GetVolume( void )
{
    return GET_WORLD().GetVolume();
}

/*****************************************************************************
 * Accessors.
 *****************************************************************************/
SkyDome&
Sky::GetSkyDome( void )
{
CHECK_TYPESIG(mSkyDome,TYPESIG_SKY_DOME);

    return *mSkyDome;
}

Sun&
Sky::GetSun( void )
{
    return *mSun;
}

} // namespace sky
